
#include <iostream>
#include <Ogre.h>
#include <OIS/OIS.h>
#include <CEGUI/CEGUI.h>
#include <OgreCEGUIRenderer.h>

#include "Constants.h"
#include "Gui.h"
//#include "GuiFrameListener.h"

using namespace Ogre;
using namespace std;

CEGUI::MouseButton convertOISMouseButtonToCegui(int buttonID)
{
    switch (buttonID)
    {
	case 0: return CEGUI::LeftButton;
	case 1: return CEGUI::RightButton;
	case 2:	return CEGUI::MiddleButton;
	case 3: return CEGUI::X1Button;
	default: return CEGUI::LeftButton;
    }
}

class BufferedInputHandler : public FrameListener, public OIS::KeyListener, public OIS::MouseListener
{
public:        
	// continue rendering
    bool mContinue;
    OIS::Keyboard *mKeyboard;
    OIS::Mouse *mMouse;
	Gui *gui;

    BufferedInputHandler(OIS::Keyboard *keyboard = 0, OIS::Mouse *mouse = 0, Gui *g=0)
    {
		 mContinue = true;
        if (keyboard)
            keyboard->setEventCallback(this);

        if (mouse)
            mouse->setEventCallback(this);
		mKeyboard = keyboard;
		mMouse = mouse;
		gui = g;
    }

	bool quit(const CEGUI::EventArgs&)
    {
        mContinue = false;
        return true;
    }

	virtual bool frameStarted(const FrameEvent &evt)
    {
        if(mMouse)
            mMouse->capture();
        if(mKeyboard) 
            mKeyboard->capture();
		gui->updateGraphics(evt);
        return mContinue && !Constants::instance()->isEnd();
    }

    // KeyListener
    virtual bool keyPressed(const OIS::KeyEvent &e) { 

		//inject key events to CEGUI
		
		CEGUI::System *sys = CEGUI::System::getSingletonPtr();
		sys->injectKeyDown(e.key);
		sys->injectChar(e.text);

		switch (e.key)
		{
			case OIS::KC_ESCAPE: 
				gui->toggleMenu();

				break;
			default:
				break;
		}
		
		return mContinue;
	}

    virtual bool keyReleased(const OIS::KeyEvent &e) { 

		CEGUI::System::getSingleton().injectKeyUp(e.key);
		switch (e.key)
		{
			case OIS::KC_ESCAPE: 
				//mContinue = false;
				break;
			default:
				gui->keyReleased(e);
				break;
		}
		return mContinue;
	}

    // MouseListener
    virtual bool mouseMoved(const OIS::MouseEvent &arg) { 

		int movingmouse=ST_MOVINGMOUSE;
		int movingcamera=ST_MOVINGCAMERA;
		int mouseState=Constants::instance()->getMouseState();
		int cameraState=Constants::instance()->getCameraState();

		if( cameraState == movingcamera)
			gui->updateCam(arg);

		if( mouseState == movingmouse ){
			CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
		}
		return true;
	}

    virtual bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { 
		switch (id)
		{
			case OIS::MB_Left:
				//gui->switchOnOff();
				CEGUI::System::getSingleton().injectMouseButtonDown(convertOISMouseButtonToCegui(id));
				gui->mousePressed(arg);
				break;
			default:
				break;
		} 
		return true;
	}

    virtual bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { 
		CEGUI::System::getSingleton().injectMouseButtonUp(convertOISMouseButtonToCegui(id));
		return true; 
	}

};


class Application
{
public:
    void go()
    {
        createRoot();
        defineResources();
        setupRenderSystem();
        createRenderWindow();
        initializeResourceGroups();
        setupScene();
        setupInputSystem();
        setupCEGUI();
        createFrameListener();
        startRenderLoop();
    }

    ~Application()
    {
		mInputManager->destroyInputObject(mKeyboard);
		mInputManager->destroyInputObject(mMouse);
		OIS::InputManager::destroyInputSystem(mInputManager);
		delete mListener;
		delete mRoot;
		delete gui;
    }

private:
    Root *mRoot;
    OIS::Keyboard *mKeyboard;
    OIS::Mouse *mMouse;
    OIS::InputManager *mInputManager;
    CEGUI::OgreCEGUIRenderer *mRenderer;
    CEGUI::System *mSystem;
    BufferedInputHandler *mListener;
	RenderWindow *win;
	Gui *gui;

	//----------------------------------------------------------------//
	CEGUI::MouseButton convertOISMouseButtonToCegui(int buttonID)
	{
		switch (buttonID)
		{
		case 0: return CEGUI::LeftButton;
		case 1: return CEGUI::RightButton;
		case 2:	return CEGUI::MiddleButton;
		case 3: return CEGUI::X1Button;
		default: return CEGUI::LeftButton;
		}
	}

    void createRoot()
    {
		mRoot = new Root();

    }
    
    void defineResources()
    {
		String secName, typeName, archName;
		ConfigFile cf;
		cf.load("resources.cfg");
		ConfigFile::SectionIterator seci = cf.getSectionIterator();
		while (seci.hasMoreElements())
		{
			secName = seci.peekNextKey();
			ConfigFile::SettingsMultiMap *settings = seci.getNext();
			ConfigFile::SettingsMultiMap::iterator i;
			for (i = settings->begin(); i != settings->end(); ++i)
			{
				typeName = i->first;
				archName = i->second;
				ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
			}
       }



    }
    
    void setupRenderSystem()
    {
		 if (!mRoot->restoreConfig() && !mRoot->showConfigDialog())
           throw Exception(52, "User canceled the config dialog!", "Application::setupRenderSystem()");

    }
    
    void createRenderWindow()
    {
		mRoot->initialise(true, "Hamer Delta Map Editor");

    }

    void initializeResourceGroups()
    {
		TextureManager::getSingleton().setDefaultNumMipmaps(5);
		ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

    }

    void setupScene()
    {
		gui = new Gui(mRoot);

    }

    void setupInputSystem()
    {
		size_t windowHnd = 0;
		std::ostringstream windowHndStr;
		OIS::ParamList pl;
		win = mRoot->getAutoCreatedWindow();

		win->getCustomAttribute("WINDOW", &windowHnd);
		windowHndStr << windowHnd;
		pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
		mInputManager = OIS::InputManager::createInputSystem(pl);
		try
		{
			mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, true));
			mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, true));
		}
		catch (const OIS::Exception &e)
		{
			throw Exception(42, e.eText, "Application::setupInputSystem");
		}
		mListener = new BufferedInputHandler(mKeyboard, mMouse, gui);
		mRoot->addFrameListener(mListener);


    }

    void setupCEGUI()
    {
		//CEGUI::WindowManager *wmgr = CEGUI::WindowManager::getSingletonPtr();
		//CEGUI::Window *quit = wmgr->getWindow((CEGUI::utf8*)"CEGUIDemo/QuitButton");
		//quit->subscribeEvent(
		//	CEGUI::PushButton::EventClicked, 
		//	CEGUI::Event::Subscriber(&BufferedInputHandler::quit, mListener));

    }

    void createFrameListener()
    {

    }

    void startRenderLoop()
    {
		mRoot->startRendering();

    }
};

#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
    try
    {
        Application app;
        app.go();
    }
    catch(Exception& e)
    {
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
        MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occurred: %s\n",
            e.getFullDescription().c_str());
#endif
    }

    return 0;
}
